﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Data;
using System.Web;
using System.Configuration;
using Gollie.WebCache;
using MySql.Data.MySqlClient;

namespace MySQL.Db
{
    public class QueryBuilder : CacheFileDependency
    {
        StringBuilder queryString;
        ResultType resultType;
        GollieDb db = GollieDb.Gollie;
        CommandType commandType = System.Data.CommandType.Text;
		private static XElement queriesNode2 = XDocument.Load(XmlQueryPath).Element("gollie").Element("queries");

        public QueryBuilder()
			: base(XmlQueryPath)
        {
        }

		public override void Initialize(params object[] args)
		{
			this.ReadQuery((string)args[0]);
		}

        internal StringBuilder QueryString
        {
            get { return this.queryString; }
        }

        internal ResultType ResultType
        {
            get { return this.resultType; }
        }

        internal System.Data.CommandType CommandType
        {
            get { return this.commandType; }
        }

        internal GollieDb Db
        {
            get { return this.db; }
        }

		internal static string XmlQueryPath
		{
			get
			{
				var section = (MySQLConfigSectionHandler)ConfigurationManager.GetSection("MySQL");
				return section.XmlQueryPath;
			}
		}

		private void ReadQuery(string name)
		{
			XElement elt = queriesNode2.Descendants("query").SingleOrDefault(p => p.Attribute("name").Value == name);
			if (elt != null)
			{
				this.SetDb(elt);

				this.SetResultType(elt);

				this.ParseQueryType(elt);
			}
			else
				throw new ArgumentException("Unknown query: " + name);
		}

		private void SetDb(XElement node)
		{
			XAttribute attr = node.Attribute("db");
			if (attr != null)
			{
				switch (attr.Value)
				{
					case "mail": this.db = GollieDb.GollieMail;
						break;
					case "ad": this.db = GollieDb.GollieAd;
						break;
				}
			}
		}

		private void SetResultType(XElement node)
		{
			string resultType = node.Attribute("resulttype").Value;

			switch (resultType)
			{
				case "multirow": this.resultType = ResultType.MultiRow;
					break;
				case "scalar": this.resultType = ResultType.Scalar;
					break;
				case "nonquery": this.resultType = ResultType.NonQuery;
					break;
				case "singlerow": this.resultType = ResultType.SingleRow;
					break;
				case "multireader": this.resultType = ResultType.MultiResultSet;
					break;
				case "identity": this.resultType = ResultType.Identity;
					break;

				default: throw new ArgumentException("Invalid resulttype: " + resultType);
			}
		}

		private void ParseQueryType(XElement node)
		{
			string leftSide = node.Attribute("type").Value;

			switch (leftSide)
			{
				case "storedprocedure":
					this.queryString = this.GetStoredProcedure(node);
					this.commandType = System.Data.CommandType.StoredProcedure;
					break;
				case "select": this.queryString = this.GetSelect(node);
					break;
				case "delete": this.queryString = this.GetDelete(node);
					break;
				case "insert": this.queryString = this.GetInsert(node);
					break;
				case "update": this.queryString = this.GetUpdate(node);
					break;
				default: throw new ArgumentException("Invalid query type: " + leftSide);
			}
		}

		private StringBuilder GetSelect(XElement node)
		{
			StringBuilder query = new StringBuilder("SELECT ", 128);

			XAttribute attr = node.Attribute("predicate");

			if (attr != null)
			{
				query.Append(" ");
				query.Append(attr.Value);
				query.Append(" ");
			}

			bool first = true;

			foreach (XElement childNode in node.Elements("column"))
			{
				if (!first)
					query.Append(", ");
				else
					first = false;

				query.Append(this.ParseSelectColumn(childNode));
				break;
			}

			query.Append(" FROM ");
			query.Append(node.Attribute("tables").Value);

			attr = node.Attribute("where");

			if (attr != null)
			{
				query.Append(" WHERE ");
				query.Append(attr.Value);
			}

			attr = node.Attribute("groupby");
			if (attr != null)
			{
				query.Append(" GROUP BY ");
				query.Append(attr.Value);
			}

			attr = node.Attribute("order");
			if (attr != null)
			{
				query.Append(" ORDER BY ");
				query.Append(attr.Value);
			}

			return query;
		}

		private string ParseSelectColumn(XElement node)
		{
			XAttribute attr = node.Attribute("alias");

			string columnName = node.Attribute("name").Value;

			StringBuilder selectColumn = new StringBuilder(columnName);

			if (attr != null)
			{
				selectColumn.Append(" AS ");
				selectColumn.Append(attr.Value);
			}

			return selectColumn.ToString();
		}

		private StringBuilder GetDelete(XElement node)
		{
			StringBuilder delete = new StringBuilder("DELETE FROM ", 128);

			delete.Append(node.Attribute("table").Value);

			XAttribute attr = node.Attribute("where");

			if (attr != null)
			{
				delete.Append(" WHERE ");
				delete.Append(attr.Value);
			}

			return delete;
		}

		private StringBuilder GetInsert(XElement node)
		{
			StringBuilder query = new StringBuilder("INSERT INTO ", 128);

			query.Append(node.Attribute("table").Value);

			query.Append(" (");

			query.Append(node.Attribute("columns").Value);

			query.Append(") VALUES(");

			query.Append(node.Attribute("values").Value);

			query.Append(")");

			return query;
		}

		private StringBuilder GetUpdate(XElement node)
		{
			StringBuilder query = new StringBuilder("UPDATE ", 128);

			query.Append(node.Attribute("table").Value);

			query.Append(" ");

			bool first = true;

			foreach (XElement childNode in node.Elements("column"))
			{
				if (first)
				{
					first = false;
					query.Append("SET ");
				}
				else
					query.Append(", ");

				query.Append(childNode.Attribute("name").Value);
				query.Append(" = ");
				query.Append(childNode.Attribute("value").Value);
				break;
			}

			XAttribute attr = node.Attribute("where");

			if (attr != null)
			{
				query.Append(" WHERE ");
				query.Append(attr.Value);
			}

			return query;
		}

		private StringBuilder GetStoredProcedure(XElement node)
		{
			StringBuilder query = new StringBuilder(node.Attribute("spname").Value);

			return query;
		}

    }
}
